#region System
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using CF.Framework.Engine.Configurations;
using CF.Framework.Engine.Drawing;
using CF.Framework.Engine.Misc;
using CF.Framework.Engine.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
#endregion

namespace CF.Framework.Engine
{
	/// <summary>
	/// Enum describes the screen transition state.
	/// </summary>
	public enum ScreenState
	{
		TransitionOn,
		Active,
		TransitionOff,
		Hidden,
	}

	public abstract class GameObject : IDisposable
	{
		#region Fields

		private bool otherScreenHasFocus;

		protected bool FirstRun = true;

		#endregion

		#region ctor
		protected GameObject()
		{
			ScreenState = ScreenState.Active;
			TransitionOffTime = TimeSpan.Zero;
			TransitionOnTime = TimeSpan.Zero;
			TransitionPosition = 1;
			AudioManager = new AudioManager();
			Components = new List<GameObject>();
			BackgroundColor = Color.Black;
			Index = 0;
			HandleInput = true;
		}
		#endregion

		#region Properties
		/// <summary>
		/// Normally when one screen is brought up over the top of another,
		/// the first screen will transition off to make room for the new
		/// one. This property indicates whether the screen is only a small
		/// popup, in which case screens underneath it do not need to bother
		/// transitioning off.
		/// </summary>
		public bool IsPopup { get; protected set; }

		/// <summary>
		/// Gets whether or not this screen is serializable. If this is true,
		/// the screen will be recorded into the screen manager's state and
		/// its Serialize and Deserialize methods will be called as appropriate.
		/// If this is false, the screen will be ignored during serialization.
		/// By default, all screens are assumed to be serializable.
		/// </summary>
		public bool IsSerializable { get; protected set; }

		/// <summary>
		/// Indicates how long the screen takes to
		/// transition on when it is activated.
		/// </summary>
		public TimeSpan TransitionOnTime { get; protected set; }

		/// <summary>
		/// Indicates how long the screen takes to
		/// transition off when it is deactivated.
		/// </summary>
		public TimeSpan TransitionOffTime { get; protected set; }

		/// <summary>
		/// Gets the current position of the screen transition, ranging
		/// from zero (fully active, no transition) to one (transitioned
		/// fully off to nothing).
		/// </summary>
		public float TransitionPosition { get; protected set; }

		/// <summary>
		/// Gets the current alpha of the screen transition, ranging
		/// from 255 (fully active, no transition) to 0 (transitioned
		/// fully off to nothing).
		/// </summary>
		public byte TransitionAlpha
		{
			get { return (byte)(255 - TransitionPosition * 255); }
		}

		public int Index { get; set; }

		public bool HandleInput { get; set; }

		/// <summary>
		/// Gets the current screen transition state.
		/// </summary>
		public ScreenState ScreenState { get; protected set; }

		public PlayerIndex? ControllingPlayer { get; internal set; }

		public ControlPad Player1
		{
			get
			{
				return GameApp.Current.Settings.ControlPads.Player1;
			}
		}

		public bool IsUnloading { get; internal set; }

		/// <summary>
		/// Checks whether this screen is active and can respond to user input.
		/// </summary>
		public bool IsActive
		{
			get
			{
				return !otherScreenHasFocus &&
					   (ScreenState == ScreenState.TransitionOn ||
						ScreenState == ScreenState.Active);
			}
		}

		/// <summary>
		/// Gets the manager that this screen belongs to.
		/// </summary>
		public ScreenManager ScreenManager
		{ 
			get 
			{
				return GameApp.Current.ScreenManager; 
			}
		}

		public AudioManager AudioManager
		{
			get;
			private set;
		}

		public List<GameObject> Components
		{
			get;
			private set;
		}

		public Color BackgroundColor { get; protected set; }

		#endregion

		#region Initialization
		internal void Initialize()
		{
			this.OnInitialize();
		}

		protected virtual void OnInitialize()
		{
		}

		/// <summary>
		/// Load graphics content for the screen.
		/// </summary>
		internal virtual void LoadContent()
		{
			this.OnLoadContent();
		}

		protected virtual void OnLoadContent()
		{
		}

		/// <summary>
		/// Unload content for the screen.
		/// </summary>
		internal virtual void UnloadContent()
		{
			this.OnUnloadContent();
			this.AudioManager.UnloadContent();
		}

		protected virtual void OnUnloadContent()
		{
		}

		internal void Ready()
		{
			OnReady();
		}

		protected virtual void OnReady()
		{
		}
		#endregion

		#region Update and Draw
		/// <summary>
		/// Allows the screen to run logic, such as updating the transition position.
		/// Unlike <see cref="HandleInput"/>, this method is called regardless of whether the screen
		/// is active, hidden, or in the middle of a transition.
		/// </summary>
		internal virtual void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
		{
			if (this.IsUnloading)
			{
				return;
			}

			this.otherScreenHasFocus = otherScreenHasFocus;
			this.otherScreenHasFocus = otherScreenHasFocus;
			
			if (coveredByOtherScreen)
			{
				// If the screen is covered by another, it should transition off.
				if (UpdateTransition(gameTime, TransitionOffTime, 1))
				{
					// Still busy transitioning.
					ScreenState = ScreenState.TransitionOff;
				}
				else
				{
					// Transition finished!
					ScreenState = ScreenState.Hidden;
				}
			}
			else
			{
				// Otherwise the screen should transition on and become active.
				if (UpdateTransition(gameTime, TransitionOnTime, -1))
				{
					// Still busy transitioning.
					ScreenState = ScreenState.TransitionOn;
				}
				else
				{
					// Transition finished!
					ScreenState = ScreenState.Active;
				}
			}

			foreach (var cmt in this.Components.OrderByDescending(p => p.Index).ToArray())
			{
				cmt.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
			}

			OnUpdate(gameTime);
		}

		protected virtual void OnUpdate(GameTime gameTime)
		{
		}

		/// <summary>
		/// Helper for updating the screen transition position.
		/// </summary>
		internal protected bool UpdateTransition(GameTime gameTime, TimeSpan time, int direction)
		{
			// How much should we move by?
			float transitionDelta;

			if (time == TimeSpan.Zero)
				transitionDelta = 1;
			else
				transitionDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds /
										  time.TotalMilliseconds);

			// Update the transition position.
			TransitionPosition += transitionDelta * direction;

			// Did we reach the end of the transition?
			if (((direction < 0) && (TransitionPosition <= 0)) ||
				((direction > 0) && (TransitionPosition >= 1)))
			{
				TransitionPosition = MathHelper.Clamp(TransitionPosition, 0, 1);
				return false;
			}

			// Otherwise we are still busy transitioning.
			return true;
		}

		/// <summary>
		/// Allows the screen to handle user input. Unlike Update, this method
		/// is only called when the screen is active, and not when some other
		/// screen has taken the focus.
		/// </summary>
		internal void DoHandleInput(InputState input)
		{
			if (HandleInput == false)
			{
				return;
			}

			foreach (var cmt in this.Components)
			{
				cmt.DoHandleInput(input);
			}

			this.OnHandleInput(input);
		}

		protected virtual void OnHandleInput(InputState input)
		{
		}

		/// <summary>
		/// This is called when the screen should draw itself.
		/// </summary>
		internal virtual void Draw(GameTime gameTime)
		{
			if (IsUnloading)
			{
				return;
			}

			var device = ScreenManager.GraphicsDevice;
			var sprite = ScreenManager.SpriteBatch;

			sprite.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);

			OnDraw(gameTime, sprite);

			sprite.End();

			foreach (var item in this.Components.OrderByDescending(p => p.Index).ToArray())
			{
				item.Draw(gameTime);
			}
		}

		protected virtual void OnDraw(GameTime gameTime, SpriteBatch spriteBatch)
		{
		}
		#endregion

		#region Public Methods

		public virtual void Dispose()
		{
			foreach(var cmt in this.Components)
			{
				cmt.Dispose();
			}

			this.Components.Clear();
		}

		/// <summary>
		/// Tells the screen to serialize its state into the given stream.
		/// </summary>
		public virtual void Serialize(Stream stream) { }

		/// <summary>
		/// Tells the screen to deserialize its state from the given stream.
		/// </summary>
		public virtual void Deserialize(Stream stream) { }

		#endregion

		#region Helper Methods
		/// <summary>
		/// A helper method which loads assets using the screen manager's
		/// associated game content loader.
		/// </summary>
		/// <typeparam name="T">Type of asset.</typeparam>
		/// <param name="assetName">Asset name, relative to the loader root
		/// directory, and not including the .xnb extension.</param>
		/// <returns></returns>
		protected T Load<T>(string assetName)
		{
			return GameApp.Current.Content.Load<T>(assetName);
		}

		protected void DisposeObject(IDisposable obj)
		{
			if (obj != null)
			{
				obj.Dispose();
			}
		}
		#endregion
	}
}
